#include "TemplateInstantiator.h"

#include "common/AttrAnalyzer.h"
#include "common/Coder.h"

using namespace rocket::common;
using namespace std;
using namespace clang;

string rocket::incparser::makeSpeclizatedDeclaration(
    const QualType &type, const string &var_name, const ASTContext &ast,
    const std::vector<TemplateArgument> &ud_tas, bool is_top) {
  assert(!type.isNull());

  if (auto ttpt = dyn_cast<TemplateTypeParmType>(type.getTypePtr())) {
    auto targ = ud_tas[ttpt->getIndex()];
    return makeVarDecl(getFullSpecArgStr(targ, ast), var_name);
  } else if (auto tst =
                 dyn_cast<TemplateSpecializationType>(type.getTypePtr())) {
    std::string tname;
    llvm::raw_string_ostream OS(tname);
#if LLVM_VERSION_MAJOR > 18
    tst->getTemplateName().dump(OS, ast);
#else
    tst->getTemplateName().dump(OS);
#endif
    OS.flush();

    tname.append("<");
#if LLVM_VERSION_MAJOR < 16
    for (unsigned int index = 0; index < tst->getNumArgs(); ++index) {
      tname.append(getFullSpecArgStr(ud_tas[index], ast) + ",");
    }
    if (tst->getNumArgs() > 0) {
      tname.pop_back();
    }
#else
    for (unsigned int index = 0; index < tst->template_arguments().size();
         ++index) {
      tname.append(getFullSpecArgStr(ud_tas[index], ast) + ",");
    }
    if (tst->template_arguments().size() > 0) {
      tname.pop_back();
    }
#endif
    tname.append(">");

    return makeVarDecl(tname, var_name);
  } else if (type->isReferenceType()) {
    return makeVarDecl(type->getPointeeType(), "&" + var_name, ast, false,
                       false);
  } else if (type->isArrayType()) {
    const ArrayType *array_type = type->getAsArrayTypeUnsafe();
    const QualType element_type = array_type->getElementType();

    std::string var_name_opt = is_top ? var_name : "(" + var_name + ")";
    const clang::ConstantArrayType *cat =
        dyn_cast<clang::ConstantArrayType>(array_type);
    return makeVarDecl(
        element_type,
        (type.getLocalQualifiers().empty()
             ? var_name
             : type.getLocalQualifiers().getAsString() + " " + var_name) +
            "[" +
            (cat ? std::to_string(cat->getSize().getZExtValue())
                 : std::string("")) +
            "]",
        ast, false, false);
  } else if (type->isFunctionPointerType()) {
    return makeVarDecl(
        type->getPointeeType(),
        "(*" +
            (type.getLocalQualifiers().empty()
                 ? var_name
                 : type.getLocalQualifiers().getAsString() + " " + var_name) +
            ")",
        ast, false, false);
  } else if (type->isPointerType()) {
    return makeVarDecl(
        type->getPointeeType(),
        "*" + (type.getLocalQualifiers().empty()
                   ? var_name
                   : type.getLocalQualifiers().getAsString() + " " + var_name),
        ast, false, false);
  } else if (auto *function_type = type->getAs<FunctionType>()) {
    return makeFuncTypeCopy(function_type, var_name, ast);
  } else {
    const TagDecl *decl = type->getAsTagDecl();
    if (decl && isAnonymous(decl)) {
      return getFullNDeclName(decl, false) + " " + var_name;
    } else {
      return getFullQTypeName(type, ast) + " " + var_name;
    }
  }
}
